/**
 * Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
 * All rights reserved.
 *
 * For the applicable distribution terms see the license.txt -file, included in
 * the distribution.
 */

#include "declarativepurchaselistmodel.h"

#include <QApplication>
#include <QDebug>
#include <QDirIterator>
#include <QFile>
#include <QtAlgorithms>
#include <QTextStream>
#include <QTimer>
#include <QEventLoop>

#include "catalog_constants.h"
#include "purchasableitem.h"
#include "purchasableitembase.h"

// uncomment this define below to have more product rows in the Catalog
// that is only for testing purposes to verify Catalog scrolling and device orientation
// change works properly
//#define INSERT_FAKE_PRODUCT_ROWS

//to be able to transfer data this type with Qt::QueuedConnection
Q_DECLARE_METATYPE(IAPClient::ProductDataList)

DeclarativePurchaseListModel::DeclarativePurchaseListModel(QObject *parent) :
    QAbstractListModel(parent),
    m_modelCommitRefCounter(0),
    m_requestId(KErrNotFound),
    m_isApplicationBusy(false)
{
    QHash<int, QByteArray> roles;
    roles[PurchasableItemBase::ProductIdRole] = "iap_productid";
    roles[PurchasableItemBase::DataReqResultRole] = "iap_reqresult";
    roles[PurchasableItemBase::TitleRole] = "iap_title";
    roles[PurchasableItemBase::ShortDescriptionRole] = "iap_shortDescr";
    roles[PurchasableItemBase::LongDescriptionRole] = "iap_longDescr";
    roles[PurchasableItemBase::PriceRole] = "iap_price";
    roles[PurchasableItemBase::DrmProtectionRole] = "iap_isDrm";
    roles[PurchasableItemBase::IsMetadataKnown] = "iap_isDataKnown";
    roles[PurchasableItemBase::IsBusy] = "iap_isBusy";
    roles[PurchasableItemBase::IsActivated] = "iap_isActivated";
    setRoleNames(roles);

    qRegisterMetaType<IAPClient::ProductDataList>("IAPClient::ProductDataList");
    qRegisterMetaType<IAPClient::UserAndDeviceDataHash>("IAPClient::UserAndDeviceDataHash");
    connect(&m_client, SIGNAL(restorableProductsReceived (int, QString, IAPClient::ProductDataList)),
            this, SLOT(restorableProductsReceived (int, QString, IAPClient::ProductDataList)),
                    Qt::QueuedConnection);

    connect(&m_client, SIGNAL(userAndDeviceDataReceived (int, QString, IAPClient::UserAndDeviceDataHash)),
            this, SLOT(userAndDeviceDataReceived (int, QString, IAPClient::UserAndDeviceDataHash )),
                               Qt::QueuedConnection);

    connect(this, SIGNAL(modelUpdated()), parent, SLOT(modelUpdated()));

    // Wait after creating IAPClient before using its API
    QEventLoop loop;
    QTimer::singleShot(0,&loop,SLOT(quit()));
    loop.exec();
    // Call IAPClient API
    readDRMCatalog();
}

DeclarativePurchaseListModel::~DeclarativePurchaseListModel()
{
    qDeleteAll(m_data);
}

QVariant DeclarativePurchaseListModel::data(const QModelIndex &index,
                                            int role) const
{
    if (index.row() == m_data.count())
        return noitemsMark(role);

    if (index.row() < 0 || index.row() > m_data.count()){
        return QVariant();
    }

    const PurchasableItemBase *record = m_data[index.row()];
    return record->metadata(role);
}

int DeclarativePurchaseListModel::rowCount(const QModelIndex &parent) const
{
    foreach (PurchasableItemBase* record, m_data){
        if (!record->isActivated())
            return m_data.count();
    }

    return m_data.count() + 1; //If no items to buy left return one more row : "No items"
}

void DeclarativePurchaseListModel::readDRMCatalog()
{
    QString resources_path = QApplication::applicationDirPath();
    resources_path.append(KProtectedCatalog);

    QString icons_path = QApplication::applicationDirPath();
    icons_path.append(KProductIcon);

    QDirIterator dirit(resources_path, QDir::Dirs, QDirIterator::NoIteratorFlags);

    while (dirit.hasNext()){
        QString dir = dirit.next();
        if (dir.contains(KResourcePattern, Qt::CaseInsensitive)){
            // breakdown the !:/private/<SID>/drm/data/resourceid_<productID>/
            // extract the product to which this file belongs
            QString product;
            QStringList elem = dir.split("_", QString::SkipEmptyParts);
            elem=elem[1].split("/", QString::SkipEmptyParts);
            product = elem[0].trimmed();
            if (product.isEmpty())
                continue;

            PurchasableItem *record = new PurchasableItem(*this, product, dir);
            beginInsertRows(QModelIndex(), rowCount(), rowCount());
            m_data << record;
            endInsertRows();
        }
    }

#ifdef INSERT_FAKE_PRODUCT_ROWS
    //insert fake rows just for test purposes
    QString fakeUrl("fakeURL");
    for (int i = 0; i < 7; i++){
        QString product = QString::number(i);
        PurchasableItem *record = new PurchasableItem(*this, product, fakeUrl);
        beginInsertRows(QModelIndex(), rowCount(), rowCount());
        m_data << record;
        endInsertRows();
    }
#endif
}

bool DeclarativePurchaseListModel::setData(const QModelIndex &index,
                                           const QVariant &value,
                                           int role)
{
    if (role < PurchasableItemBase::ProductIdRole || index.row() == m_data.count())
        return false;

    PurchasableItemBase *record = const_cast<PurchasableItemBase*>(m_data[index.row()]);
    return record->setMetadata(value, role);
}

void DeclarativePurchaseListModel::beginModelUpdate()
{
    if (!m_modelCommitRefCounter)
        beginResetModel();
    m_modelCommitRefCounter++;
}

void DeclarativePurchaseListModel::commitModelUpdate()
{
    m_modelCommitRefCounter--;
    if (m_modelCommitRefCounter < 0)
        m_modelCommitRefCounter = 0;

    if (!m_modelCommitRefCounter){
        endResetModel();

        // notify parent model was updated
        if (allowModelUpdateNotifications()){
            emit modelUpdated();
        }
    }
}

IAPClient &DeclarativePurchaseListModel::client()
{
    return m_client;
}

void DeclarativePurchaseListModel::buy(const QModelIndex &index)
{
    qDebug() << "DeclarativePurchaseListModel::buy " << index;
    const_cast<PurchasableItemBase*>(record(index))->purchase();
}

PurchasableItemBase *DeclarativePurchaseListModel::record(int index) const
{
    if (index == m_data.count())
        return NULL;

    return (m_data[index]);
}

PurchasableItemBase *DeclarativePurchaseListModel::record(const QModelIndex &index) const
{
    if (index.row() == m_data.count())
        return NULL;

    return (m_data[index.row()]);
}


void DeclarativePurchaseListModel::restorableProductsReceived(int requestId,
                                                              QString status,
                                                              IAPClient::ProductDataList items)
{
    if(requestId != m_requestId)
        return;

    // Note , request id value is used as condition to busy indicator
    // it is needed to set the value to initial state when the request is done
    m_requestId = KErrNotFound;

    qDebug(__FUNCTION__);

    beginResetModel();
    PurchasableItemBase* item;
    foreach(item, m_data)
    {
        item->setMetadata(QVariant(false), PurchasableItemBase::IsBusy);
        if(item->isIncluded(items))
        {
            //trigger restoration
            item->restore();
        }
    }
    endResetModel();
}

void DeclarativePurchaseListModel::restoreProducts()
{
    if (m_requestId >= 0){
        qDebug() << "Catalog is busy. you can set only one request at time";
        return;
    }

    qDebug(__PRETTY_FUNCTION__);

    m_requestId = m_client.getRestorableProducts();
    if (m_requestId < 0)
        return; // failure hapened

    beginResetModel();
    PurchasableItemBase* item;

    foreach (item, m_data){
        item->setMetadata(QVariant(true), PurchasableItemBase::IsBusy);
    }
    endResetModel();
}

void DeclarativePurchaseListModel::getUserInfo()
{
    if (m_requestId >= 0){
        qDebug() << "Catalog is busy. you can set only one request at time";
        return;
    }

    qDebug(__PRETTY_FUNCTION__);

    m_requestId = m_client.getUserAndDeviceId();
    if (m_requestId < 0)
        return; // failure hapened
        
    m_isApplicationBusy = true;
    emit applicationBusyChanged();
}

void DeclarativePurchaseListModel::userAndDeviceDataReceived (int requestId, QString status,
                                                              IAPClient::UserAndDeviceDataHash userdata)
{
    if(requestId != m_requestId)
        return;

    // Note , request id value is used as condition to busy indicator
    // it is needed to set the value to initial state when the request is done
    m_requestId = KErrNotFound;
    m_isApplicationBusy = false;
    emit applicationBusyChanged();

    qDebug(__FUNCTION__);

    qDebug() << "---data begin---" ;
    qDebug() << "\t" << userdata.value("accountdigest").toString();
    qDebug() << "\t" << userdata.value("imeidigest").toString();
    qDebug() << "\t" << userdata.value("imsidigest").toString();
    qDebug() << "\t" << userdata.value("country").toString();
    qDebug() << "\t" << userdata.value("language").toString();
    qDebug() << "\t" << userdata.value("devicemodel").toString();
    qDebug() << "---end data---" ;
}

bool DeclarativePurchaseListModel::allowModelUpdateNotifications()
{
    bool result = true;
    foreach (PurchasableItemBase* item, m_data)
    {
         result &= item->isMetaDataKnown();
    }
    return result;
}

void DeclarativePurchaseListModel::purchaseFlowFinished(bool status)
{
    emit purchaseCompleted(status);
}

PurchasableItemBase *DeclarativePurchaseListModel::find(QString productID) const
{
    foreach (PurchasableItemBase* item, m_data)
    {
        QVariant id = item->metadata(PurchasableItemBase::ProductIdRole);
        if (id.toString().compare(productID) == 0)
            return item;
    }
    return NULL;
}

QVariant DeclarativePurchaseListModel::noitemsMark(int role) const
{
    switch (role)
    {
        case PurchasableItemBase::ProductIdRole:
            return "";
        case PurchasableItemBase::DataReqResultRole:
            return "";
        case PurchasableItemBase::TitleRole:
            return "No items";
        case PurchasableItemBase::ShortDescriptionRole:
            return "";
        case PurchasableItemBase::LongDescriptionRole:
            return "";
        case PurchasableItemBase::PriceRole:
            return "";
        case PurchasableItemBase::DrmProtectionRole:
            return true;
        case PurchasableItemBase::IsMetadataKnown:
            return true;
        case PurchasableItemBase::IsBusy:
            return false;
        case PurchasableItemBase::IsActivated:
            return false;
        default:
            break;
    }
    return QVariant();
}
